/*
 * Copyright The WildFly Authors
 * SPDX-License-Identifier: Apache-2.0
 */

package org.jboss.as.ee.component;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.jboss.vfs.VirtualFile;

/**
 * @author John Bailey
 */
public class EEApplicationDescription {
    //these are only written to by a single top level processor, so do not need to be synchronized
    private final Map<String, List<ViewInformation>> componentsByViewName = new HashMap<String, List<ViewInformation>>();
    private final Map<String, List<Description>> componentsByName = new HashMap<String, List<Description>>();

    //this must be synchronized for writing
    private final Map<String, List<MessageDestinationMapping>> messageDestinationJndiMapping = new HashMap<String, List<MessageDestinationMapping>>();

    /**
     * Add a component to this application.
     *
     * @param description    the component description
     * @param deploymentRoot
     */
    public void addComponent(final ComponentDescription description, final VirtualFile deploymentRoot) {
        for (final ViewDescription viewDescription : description.getViews()) {
            List<ViewInformation> viewComponents = componentsByViewName.get(viewDescription.getViewClassName());
            if (viewComponents == null) {
                viewComponents = new ArrayList<ViewInformation>(1);
                componentsByViewName.put(viewDescription.getViewClassName(), viewComponents);
            }
            viewComponents.add(new ViewInformation(viewDescription, deploymentRoot, description.getComponentName()));
        }
        List<Description> components = componentsByName.get(description.getComponentName());
        if (components == null) {
            componentsByName.put(description.getComponentName(), components = new ArrayList<Description>(1));
        }
        components.add(new Description(description, deploymentRoot));
    }

    /**
     * Add a message destination to the application
     *
     * @param name           The message destination name
     * @param resolvedName   The resolved JNDI name
     * @param deploymentRoot The deployment root
     */
    public void addMessageDestination(final String name, final String resolvedName, final VirtualFile deploymentRoot) {
        List<MessageDestinationMapping> components = messageDestinationJndiMapping.get(name);
        if (components == null) {
            messageDestinationJndiMapping.put(name, components = new ArrayList<MessageDestinationMapping>(1));
        }
        components.add(new MessageDestinationMapping(resolvedName, deploymentRoot));
    }

    /**
     * Get all views that have the given type in the application
     *
     * @param viewType The view type
     * @return All views of the given type
     */
    public Set<ViewDescription> getComponentsForViewName(final String viewType, final VirtualFile deploymentRoot) {
        final List<ViewInformation> info = componentsByViewName.get(viewType);

        if (info == null) {
            return Collections.<ViewDescription>emptySet();
        }
        final Set<ViewDescription> ret = new HashSet<ViewDescription>();
        final Set<ViewDescription> currentDep = new HashSet<ViewDescription>();
        for (ViewInformation i : info) {
            if (deploymentRoot.equals(i.deploymentRoot)) {
                currentDep.add(i.viewDescription);
            }
            ret.add(i.viewDescription);
        }
        if(!currentDep.isEmpty()) {
            return currentDep;
        }
        return ret;
    }

    /**
     * Get all components in the application that have the given name
     *
     * @param componentName  The name of the component
     * @param deploymentRoot The deployment root of the component doing the lookup
     * @return A set of all views for the given component name and type
     */
    public Set<ComponentDescription> getComponents(final String componentName, final VirtualFile deploymentRoot) {
        if (componentName.contains("#")) {
            final String[] parts = componentName.split("#");
            String path = parts[0];
            if (!path.startsWith("../")) {
                path = "../" + path;
            }
            final VirtualFile virtualPath = deploymentRoot.getChild(path);
            final String name = parts[1];
            final List<Description> info = componentsByName.get(name);
            if (info == null) {
                return Collections.emptySet();
            }
            final Set<ComponentDescription> ret = new HashSet<ComponentDescription>();
            for (Description i : info) {
                //now we need to check the path
                if (virtualPath.equals(i.deploymentRoot)) {
                    ret.add(i.componentDescription);
                }
            }
            return ret;
        } else {
            final List<Description> info = componentsByName.get(componentName);
            if (info == null) {
                return Collections.emptySet();
            }
            final Set<ComponentDescription> all = new HashSet<ComponentDescription>();
            final Set<ComponentDescription> thisDeployment = new HashSet<ComponentDescription>();
            for (Description i : info) {
                all.add(i.componentDescription);
                if (i.deploymentRoot.equals(deploymentRoot)) {
                    thisDeployment.add(i.componentDescription);
                }
            }
            //if there are multiple e
            if (all.size() > 1) {
                return thisDeployment;
            }
            return all;
        }
    }

    /**
     * Get all views in the application that have the given name and view type
     *
     * @param componentName  The name of the component
     * @param viewName       The view type
     * @param deploymentRoot The deployment root of the component doing the lookup
     * @return A set of all views for the given component name and type
     */
    public Set<ViewDescription> getComponents(final String componentName, final String viewName, final VirtualFile deploymentRoot) {
        final List<ViewInformation> info = componentsByViewName.get(viewName);
        if (info == null) {
            return Collections.<ViewDescription>emptySet();
        }
        if (componentName.contains("#")) {
            final String[] parts = componentName.split("#");
            String path = parts[0];
            if (!path.startsWith("../")) {
                path = "../" + path;
            }
            final VirtualFile virtualPath = deploymentRoot.getChild(path);
            final String name = parts[1];
            final Set<ViewDescription> ret = new HashSet<ViewDescription>();
            for (ViewInformation i : info) {
                if (i.beanName.equals(name)
                        // now we need to check the path
                        && virtualPath.equals(i.deploymentRoot)) {
                    ret.add(i.viewDescription);
                }
            }
            return ret;
        } else {
            final Set<ViewDescription> all = new HashSet<ViewDescription>();
            final Set<ViewDescription> thisDeployment = new HashSet<ViewDescription>();
            for (ViewInformation i : info) {
                if (i.beanName.equals(componentName)) {
                    all.add(i.viewDescription);
                    if (i.deploymentRoot.equals(deploymentRoot)) {
                        thisDeployment.add(i.viewDescription);
                    }
                }
            }
            if (all.size() > 1) {
                return thisDeployment;
            }
            return all;
        }
    }

    /**
     * Resolves a message destination name into a JNDI name
     */
    public Set<String> resolveMessageDestination(final String messageDestName, final VirtualFile deploymentRoot) {

        if (messageDestName.contains("#")) {
            final String[] parts = messageDestName.split("#");
            String path = parts[0];
            if (!path.startsWith("../")) {
                path = "../" + path;
            }
            final VirtualFile virtualPath = deploymentRoot.getChild(path);
            final String name = parts[1];
            final Set<String> ret = new HashSet<String>();
            final List<MessageDestinationMapping> data = messageDestinationJndiMapping.get(name);
            if (data != null) {
                for (final MessageDestinationMapping i : data) {
                    //now we need to check the path
                    if (virtualPath.equals(i.deploymentRoot)) {
                        ret.add(i.jndiName);
                    }
                }
            }
            return ret;
        } else {
            final Set<String> all = new HashSet<String>();
            final Set<String> thisDeployment = new HashSet<String>();
            final List<MessageDestinationMapping> data = messageDestinationJndiMapping.get(messageDestName);
            if (data != null) {
                for (final MessageDestinationMapping i : data) {
                    all.add(i.jndiName);
                    if (i.deploymentRoot.equals(deploymentRoot)) {
                        thisDeployment.add(i.jndiName);
                    }
                }
            }
            if (all.size() > 1) {
                return thisDeployment;
            }
            return all;
        }
    }

    private static class ViewInformation {
        private final ViewDescription viewDescription;
        private final VirtualFile deploymentRoot;
        private final String beanName;

        ViewInformation(final ViewDescription viewDescription, final VirtualFile deploymentRoot, final String beanName) {
            this.viewDescription = viewDescription;
            this.deploymentRoot = deploymentRoot;
            this.beanName = beanName;
        }
    }

    private static class Description {
        private final ComponentDescription componentDescription;
        private final VirtualFile deploymentRoot;

        Description(final ComponentDescription componentDescription, final VirtualFile deploymentRoot) {
            this.componentDescription = componentDescription;
            this.deploymentRoot = deploymentRoot;
        }
    }

    private static final class MessageDestinationMapping {
        private final String jndiName;
        private final VirtualFile deploymentRoot;

        MessageDestinationMapping(final String jndiName, final VirtualFile deploymentRoot) {
            this.jndiName = jndiName;
            this.deploymentRoot = deploymentRoot;
        }
    }

}
